package charts; 

import flash.display.Sprite;
import charts.series.Element;
import flash.geom.Point;
import elements.axis.XAxisLabels;
import charts.Structs.KeyInfoStruct;
import charts.Structs.ClosestStruct;

/**
 * NOTE:
 *
 * Use fucking composite. Every fucking class that derives from Base,
 * overrides resize(), just implement it on that class. Use duck-typing
 * techniques. Derive from Sprite instead, because that probably supplies
 * the interface for resize.
 *
 * Don't inherit, because you'll end up overloading the members, or
 * bloating up the class types. Eejits.
 *
 */

class Base<ValueType> extends Sprite {
	// accessed by the Keys object to display the key
	var key:String;
	var font_size:Float;
	
	public var colour:Float;
	public var line_width:Float;
	public var circle_size:Float;
	
	//
	// hold the Element values, for lines this is an
	// array of string Y values, for Candle it is an
	// array of string 'high,open,low,close' values,
	// for scatter it is 'x,y' etc...
	//
	public var values:Array<ValueType>;
	
	var axis:Float;
	
	public function new()
	{super();}
	
	public function get_colour(): Float {
		return this.colour;
	}
	
	//
	// return an array of key info objects:
	//
	public function get_keys(): Array<KeyInfoStruct> {
		
		var arr = new Array<KeyInfoStruct>();
		
		// some lines may not have a key
		if( this.font_size > 0 && this.key != '' )
		{
/*			/// NOTE: this shit is illegal in HaXe
			var obj:Dynamic = {
					'text'			:this.key
					, 'font-size'		:this.font_size
					, 'colour'		:this.get_colour()
				};
*/
/*
			/// The proper idiom is (?):
			var obj:Dynamic<String> = new Dynamic();
			obj['text'] = this.key;
			obj['font-size'] = this.font_size;
			obj['colour'] = this.get_colour();
			arr.push(obj);
*/
			var obj:KeyInfoStruct = {
				text		: this.key
				, font_size	: this.font_size
				, colour  	: this.get_colour()
			};
			
			arr.push(obj);
		}

		
		return arr;
	}
	
	
	// NOTE: this seems to be a debugging function, it's outcommented in many places if used at all...
	//
	// whatever sets of data that *may* be attached to the right
	// Y Axis call this to see if they are attached to it or not.
	// All lines, area and bar charts call this.
	//
/*	function which_axis_am_i_attached_to( data:Array<Dynamic>, i:Float ): Float {
		if (data['show_y2'] == undefined) // TODO - check: Does this ever happen?
			return 1;

		if( data['show_y2'] != 'false' ) // TODO - check: Does this ever happen?
			if( data['y2_lines'] != undefined ) // TODO - check: Does this ever happen?
			{
				var arr:Array<Dynamic> = data.y2_lines.split(",");
				var pos:Int = arr.indexOf( i.toString() );
					
				if ( pos == -1 )
					return 1;

				return 2;	// <-- this line found in y2_lines, so it is attached to axis 2 (right axis)
			}

		return 1;
	}*/
		
	
	/**
	 * may be called by main.as to make the X Axis labels
	 * @return
	 */
	public function get_max_x():Float {
		
		var max:Float = Math.NEGATIVE_INFINITY;
		//
		// count the non-mask items:
		//
		for ( i in 0...this.numChildren) {
			if ( Std.is( this.getChildAt(i), ValueType) ) {
				
				var e:Element = cast( this.getChildAt(i), Element);  // TODO - necessary?
				max = Math.max( max, e.get_x() );
			}
		}

		return max;
	}
	
	public function get_min_x():Float {
		
		var min:Float = Math.INFINITY;
		//
		// count the non-mask items:
		//
		for ( i in 0...this.numChildren) {
			if ( Std.is( this.getChildAt(i), ValueType) ) {
				
				var e:Element = cast( this.getChildAt(i), Element); // TODO - necessary?
				min = Math.min( min, e.get_x() );
			}
		}

		return min;
	}
	
	//
	// this should be overriden
	//
	//public function resize( sc:ScreenCoordsBase ):Void{}
	
	//public function draw( val:String, mc:Dynamic ):Void {}

	//
	// TODO: old remove when tooltips tested
	//
	public function closest( x:Float, y:Float ): ClosestStruct {
		var shortest:Float = Math.INFINITY;
		var closest:Element = null;
		var dx:Float;
		
		for ( i in 0...this.numChildren) {
		
			//
			// some of the children will will mask
			// Sprites, so filter those out:
			//
			if( Std.is( this.getChildAt(i), ValueType) ) {
				
				var e:Element = cast( this.getChildAt(i), Element); // TODO - necessary?
				e.set_tip( false );
			
				dx = Math.abs( x -e.x );
			
				if( dx < shortest )	{
					shortest = dx;
					closest = e;
				}
			}
		}
		
		var dy:Float = 0;
		if( closest )
			dy = Math.abs( y - closest.y );
			
		return { element:closest, distance_x:shortest, distance_y:dy };
	}
	
	//
	// Line and bar charts will normally only have one
	// Element at any X position, but when using Radar axis
	// you may get many at any give X location.
	//
	// Scatter charts can have many items at the same X position
	//
	public function closest_2( x:Float, y:Float ): Array<ValueType> {

		// get the closest elements X value
		var x:Int = closest_x(x);
		var arr:Array<ValueType> = this.get_all_at_this_x_pos(x);
		
		// Tr.acesPortMe('tmp.length', tmp.length);
		
		var closest:Array<ValueType> = this.get_closest_y(arr, y);
		var dy:Int = Math.abs( y - closest.y );
		// Tr.acesPortMe('closest.length', closest.length);
		
		return closest;
	}
	
	//
	// get the X value of the closest Points to the mouse
	//
	function closest_x( x:Float ):Float {
		
		var closest:Float = Math.INFINITY;
		var p:flash.geom.Point;
		var x_pos:Float;
		var dx:Float;
		
		for ( i in 0...this.numChildren) {
		
			//
			// some of the children will will mask
			// Sprites, so filter those out:
			//
			if( Std.is( this.getChildAt(i), Element) ) {  // TODO - necessary?
	
				var e:Element = cast( this.getChildAt(i), Element);  // TODO - necessary?
			
				p = e.get_mid_point();
				dx = Math.abs( x - p.x );

				if( dx < closest )	{
					closest = dx;
					x_pos = p.x;
				}
			}
		}
		
		return x_pos;
	}
	
	//
	// get all the elements at this X position
	// BarStack overrides this
	//
	function get_all_at_this_x_pos( x:Float ):Array<ValueType> {
		
		var arr = new Array<Element>();
		var p:flash.geom.Point;
		var e:Element;
		
		for ( i in 0...this.numChildren) {
		
			// some of the children will will mask
			// Sprites, so filter those out:
			//
			if( Std.is( this.getChildAt(i), ValueType) ) { // TODO - find out if this is actually necessary with templates...
	
				e = cast( this.getChildAt(i), Element);  // TODO - necessary?
				
				//
				// Point elements are invisible by default.
				//
				// Prevent invisible Points from showing tooltips
				// For scatter line area
				//if (e.visible)
				//{
					p = e.get_mid_point();
					if ( p.x == x )
						arr.push( e );
				//}
			}
		}
		
		return arr;
	}
	
	//
	// scatter charts may have many elements in the same
	// x, y location
	//
	function get_closest_y( elements:Array<Element>, y:Float):Array<Element> {
		
		var y_min:Float = Math.INFINITY;
		var dy:Float;
		var closest = new Array<Element>();
		var p:flash.geom.Point;
		var e:Element;
		
		// get min Y distance
		for ( e in elements ) {
			
			p = e.get_mid_point();
			dy = Math.abs( y - p.y );
			
			y_min = Math.min( dy, y_min );
		}
		
		// select all elements at this Y pos
		for ( e in elements ) {
			
			p = e.get_mid_point();
			dy = Math.abs( y - p.y );
			if( dy == y_min )
				closest.push(e);
		}

		return closest;
	}
	
	//
	// scatter charts may have many elements in the same
	// x, y location
	//
	public function mouse_proximity( x:Float, y:Float ): Array<Element> {
		
		var closest:Float = Math.INFINITY;
		var p:flash.geom.Point;
		var i:Float;
		var e:Element;
		var mouse:flash.geom.Point = new flash.geom.Point(x, y);
		
		//
		// find the closest elements
		//
		for ( i in 0...this.numChildren) {
		
			// filter mask Sprites
			if( Std.is( this.getChildAt(i), Element) ) {
	
				e = cast( this.getChildAt(i), Element);
				closest = Math.min( flash.geom.Point.distance(e.get_mid_point(), mouse), closest );
			}
		}
		
		//
		// grab all elements at this distance
		//
		var close:Array<Element> = [];
		for ( i in 0...this.numChildren) {
		
			// filter mask Sprites
			if( Std.is( this.getChildAt(i), Element) ) {
	
				e = cast( this.getChildAt(i), Element);
				if ( flash.geom.Point.distance(e.get_mid_point(), mouse) == closest )
					close.push(e);
			}
		}
		
		return close;
	}
	
	
	
	//
	// this is a backup function so if the mouse leaves the
	// movie for some reason without raising the mouse
	// out event (this happens if the user is wizzing the mouse about)
	//
	public function mouse_out():Void {
		for ( i in 0...this.numChildren) {
			
			// filter out the mask elements in line charts
			if( Std.is( this.getChildAt(i), Element) ) {
				
				var e:Element = cast( this.getChildAt(i), Element);
				e.set_tip(false);
			}
		}
	}
	
	
	//
	// index of item (bar, Point, pie slice, horizontal bar) may be used
	// to look up its X value (bar,Point) or Y value (H Bar) or used as
	// the sequence number (Pie)
	//
	function get_element( index:Int, value:Dynamic ): ValueType {
		return null; // TODO - figure out why this is empty, compare with original. Weird.
		// TODO - also figure out what 'value' is supposed to do
	}
	
	public function add_values(values:Array<ValueType>):Void {
		
		// keep track of the X position (column)
		var index:Int = 0;
		
		for ( val in values )
		{
			var tmp:ValueType;
			
			// filter out the 'null' values
			if( val != null )
			{
				tmp = this.get_element( index, val );
				
				if( tmp.line_mask != null )
					this.addChild( tmp.line_mask );
					
				this.addChild( tmp );
			}
			
			index++;
		}
	}

// 	// TODO - implement fucking interface on missing ones...
// 	public static function add_values(values:Array<Dynamic>):Void {
// 		
// 		// keep track of the X position (column)
// 		var index:Int = 0;
// 		
// 		for ( v in values )
// 		{
// 			var element:Element;
// 			
// 			// filter out the 'null' values
// 			if( v != null )
// 			{
// 				element = obj.get_element( index, v );
// 				
// 				if( element.line_mask != null )
// 					obj.addChild( obj.line_mask );
// 					
// 				obj.addChild( element );
// 			}
// 			
// 			index++;
// 		}
// 	}

	
	/**
		* See ObjectCollection tooltip_replace_labels
		* 
		* @param	labels
		*/
	public function tooltip_replace_labels( labels:XAxisLabels ):Void {
		for ( i in 0...this.numChildren) {
			
			// filter out the mask elements in line charts
			if( Std.is( this.getChildAt(i), ValueType) ) {
				
				var e:ValueType = cast( this.getChildAt(i), Element);
				e.tooltip_replace_labels( labels );
			}
		}
	}
	
	public function die():Void {
		
		//for (i:Int = 0; i < this.numChildren; i++ )
		for (i in 0...this.numChildren)
			if ( Std.is( this.getChildAt(i), ValueType) ) {
				var e:ValueType = cast( this.getChildAt(i), Element);
				e.die();
			}
		
		while ( this.numChildren > 0 )
			this.removeChildAt(0);
	}
}
